package v1

import (
	"context"
	"fmt"
	"github.com/go-logr/logr"
	v1 "k8s.io/api/batch/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"reflect"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

type JobSpec struct {
	InitImage    string            `json:"initImage,omitempty"`
	Image        string            `json:"image,omitempty"`
	HostIp       string            `json:"hostIp"`
	ClusterName  string            `json:"clusterName"`
	ClusterHost  string            `json:"clusterHost"`
	ClusterPort  *int              `json:"clusterPort"`
	ExposePort   *int              `json:"exposePort"`
	Server       *int              `json:"server,omitempty"`
	Agent        *int              `json:"agent,omitempty"`
	KafkaUrl     string            `json:"kafkaUrl,omitempty"`
	NodeSelector map[string]string `json:"nodeSelector,omitempty" protobuf:"bytes,7,rep,name=nodeSelector"`
}

type OwnJob struct {
	Spec v1.JobSpec `json:"spec"`
}

func (ownJob *OwnJob) MakeOwnResource(instance *K3d, logger logr.Logger,
	scheme *runtime.Scheme) (interface{}, error) {

	// new a job object
	job := &v1.Job{
		// metadata field inherited from owner Unit
		//ObjectMeta: instance.ObjectMeta,
		ObjectMeta: metav1.ObjectMeta{Name: instance.Name, Namespace: instance.Namespace, Labels: instance.Labels},
		Spec:       ownJob.Spec,
	}

	// add ControllerReference for job，the owner is Unit object
	if err := controllerutil.SetControllerReference(instance, job, scheme); err != nil {
		msg := fmt.Sprintf("set controllerReference for job %s/%s failed", instance.Namespace, instance.Name)
		logger.Error(err, msg)
		return nil, err
	}
	return job, nil
}

// Check if the job already exists
func (ownJob *OwnJob) OwnResourceExist(instance *K3d, client client.Client,
	logger logr.Logger) (bool, interface{}, error) {

	found := &v1.Job{}
	err := client.Get(context.TODO(), types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}, found)
	if err != nil {
		if errors.IsNotFound(err) {
			return false, nil, nil
		}
		msg := fmt.Sprintf("job %s/%s found, but with error", instance.Namespace, instance.Name)
		logger.Error(err, msg)
		return true, found, err
	}
	return true, found, nil
}

func (ownJob *OwnJob) UpdateOwnResourceStatus(instance *K3d, client client.Client,
	logger logr.Logger) (*K3d, error) {

	// 获取job的状态
	found := &v1.Job{}
	err := client.Get(context.TODO(), types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}, found)
	if err != nil {
		msg := fmt.Sprintf("get Unit %s/%s own job status error", instance.Namespace, instance.Name)
		logger.Error(err, msg)
		return instance, err
	}

	// 将job的状态更新到Unit.status.job中
	//instance.Status.Basejob = found.Status
	//instance.Status.LastUpdateTime = metav1.Now()

	return instance, nil
}

// apply this own resource, create or update
func (ownJob *OwnJob) ApplyOwnResource(instance *K3d, client client.Client,
	logger logr.Logger, scheme *runtime.Scheme) error {
	// make job object
	job, err := ownJob.MakeOwnResource(instance, logger, scheme)
	if err != nil {
		return err
	}
	newJob := job.(*v1.Job)

	// assert if job already exist
	exist, found, err := ownJob.OwnResourceExist(instance, client, logger)
	if err != nil {
		return err
	}

	// apply the job object just make
	if !exist {
		// if job not exist，then create it
		msg := fmt.Sprintf("job %s/%s not found, create it!", newJob.Namespace, newJob.Name)
		logger.Info(msg)
		if err := client.Create(context.TODO(), newJob); err != nil {
			return err
		}
		return nil

	} else {
		foundJob := found.(*v1.Job)
		// if job exist with change，then try to update it
		if !reflect.DeepEqual(newJob.Spec, foundJob.Spec) {
			msg := fmt.Sprintf("Updating job %s/%s", newJob.Namespace, newJob.Name)
			logger.Info(msg)
			return client.Update(context.TODO(), newJob)
		}
		return nil
	}
}
